package ai.people.core.grpc.grpcinterceptor;

import ai.people.netmon.framework.constant.NmSystemConstant;
import ai.people.netmon.framework.exception.enums.AuthExceptionEnum;
import ai.people.netmon.framework.utils.AssertUtils;
import ai.people.netmon.utils.CookieUtil;
import io.grpc.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Optional;

/**
 * grpc客户端端Token拦截器
 *
 * @author yuanqinglong
 * @date 2022/6/4 22:52
 */
public class GrpcClientTokenInterceptor implements ClientInterceptor {


    /**
     * Intercept {@link ClientCall} creation by the {@code next} {@link Channel}.
     *
     * <p>Many variations of interception are possible. Complex implementations may return a wrapper
     * around the result of {@code next.newCall()}, whereas a simpler implementation may just modify
     * the header metadata prior to returning the result of {@code next.newCall()}.
     *
     * <p>{@code next.newCall()} <strong>must not</strong> be called under a different {@link Context}
     * other than the current {@code Context}. The outcome of such usage is undefined and may cause
     * memory leak due to unbounded chain of {@code Context}s.
     *
     * @param method      the remote method to be called.
     * @param callOptions the runtime options to be applied to this call.
     * @param next        the channel which is being intercepted.
     * @return the call object for the remote operation, never {@code null}.
     */
    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
        return new ForwardingClientCall.SimpleForwardingClientCall<>(
                next.newCall(method, callOptions)) {
            @Override
            public void start(final Listener<RespT> responseListener, final Metadata headers) {
                Metadata.Key<String> tokenKey = Metadata.Key.of(NmSystemConstant.AUTH_COOKIE_TOKEN_ID, Metadata.ASCII_STRING_MARSHALLER);
                String token = Optional.ofNullable(headers.get(tokenKey)).orElseGet(() -> {
                    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
                    return CookieUtil.getCookieValue(request, NmSystemConstant.AUTH_COOKIE_TOKEN_ID);
                });
                AssertUtils.isFalse(StringUtils.isEmpty(token), AuthExceptionEnum.INVALID_TOKEN);
                headers.put(tokenKey, token);
                super.start(responseListener, headers);
            }
        };
    }
}